﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace WLive.WLiveObjects
{

    public interface IOperational
    {
        
    }
    public interface IOperationalFolders : IOperational
    {
        void Copy(string newlocation);
        void Move(string newlocation);
        void Delete();
        void Update();
    }
    
    public interface IOperationalFiles : IOperationalFolders
    {
        void UpdateContent(Stream file, bool append = false);
    }
    public abstract class AGetOp<T>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="objectid">Object ID - FOLDER_ID/FILE_ID/...</param>
        /// <returns>Return object get by their ID</returns>
        public static T Get(string objectid)
        {
            try
            {
                return Requester.Request<T>(new RequestObject { url = UrlBuilder1.Build(objectid) }).ElementAt(0);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
    public abstract class AOperational<T>:AGetOp<T>
    {
        private static string[] filestype = { "photo", "audio", "notebook", "file" };
        private static string[] folderstype = { "folder", "album" };
        public string id;
        [Writable]
        public string description;
        [Writable]
        public string name;
        public string parent_id;
        public static int download_buffer_size = 512;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="owoption">Overwrite option</param>
        /// <returns>Return string representation of overwrite option</returns>
        protected static string GetOverWriteOption(OverWriteOption owoption)
        {
            switch (owoption)
            {
                case OverWriteOption.CHOOSENEWNAME:return "choosenewname";
                case OverWriteOption.DONOTOVERWRITE:return "false";
                case OverWriteOption.OVERWRITE: return "true";
            }
            return "";
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="location">FOLDER_ID</param>
        /// <param name="name">Name of object which you want to find</param>
        /// <returns>Return object if object exists in location otherwise null</returns>
        public static T GetByName(string location, string name)
        {
            string type = Activator.CreateInstance(typeof(T)).GetType().Name.ToLower();
            foreach (File f in Folder.GetFiles(location))
            {
                if (type == "file" && filestype.Contains(f.type.ToLower()))
                    if (f.name.ToLower() == name.ToLower())
                        return Get(f.id);
                    else if ((type == "folder" || type == "album") && folderstype.Contains(f.type.ToLower()))
                        if (f.name.ToLower() == name.ToLower())
                            return Get(f.id);
                        else
                            if (type == f.type && f.name.ToLower() == name)
                            return Get(f.id);
            }
            return default(T);
        }
    }
    public abstract class Operational<T>:AOperational<T>, IOperationalFolders
    {
        /// <summary>
        /// Create new object
        /// </summary>
        /// <param name="locationid">Location id where you want object create</param>
        /// <param name="name">Name of object</param>
        /// <param name="description">Description object</param>
        /// <returns>Created object</returns>
        public static T Create(//string locationid, string name, string description, 
            T obj)
        {
            if (string.IsNullOrEmpty((obj as AOperational<T>).parent_id))
                (obj as AOperational<T>).parent_id = "me/skydrive";
            string data = JSONDataBuilder.Build(obj); //"{\"name\": \"" + name + "\",\r\n \"description\": \"" + description + "\"}";
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build((obj as AOperational<T>).parent_id), method = "POST", content_type = "application/json" };
                ro.SetData(data);
                return Requester.Request<T>(ro).ElementAt(0);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Update object properties - name and description. You can use this method for rename object.
        /// </summary>
        public virtual void Update()
        {
            string data = JSONDataBuilder.Build(this);
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "PUT", content_type = "application/json" };
                ro.SetData(data);
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Copy object into new location.
        /// </summary>
        /// <param name="newlocationid">Location id where you want to copy object.</param>
        public virtual void Copy(string newlocationid)
        {
            if (string.IsNullOrEmpty(newlocationid))
                newlocationid = "me/skydrive";
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "COPY", content_type = "application/json" };
                ro.SetData("{ \"destination\": \"" + newlocationid + "\"}");
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// MOve object into new location
        /// </summary>
        /// <param name="newlocationid">Location id where you want to move object</param>
        public virtual void Move(string newlocationid)
        {
            if (string.IsNullOrEmpty(newlocationid))
                newlocationid = "me/skydrive";
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "MOVE", content_type = "application/json" };
                ro.SetData("{ \"destination\": \"" + newlocationid + "\"}");
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Delete object
        /// </summary>
        public virtual void Delete()
        {
            try
            {
                Requester.Request<T>(new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "DELETE" });
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    public abstract class AFiles<T>:AOperational<T>, IOperationalFiles
    {
        public string source;

        /// <summary>
        /// Create object with content
        /// </summary>
        /// <param name="locationid">Location id where you want to create object</param>
        /// <param name="name">Name of object</param>
        /// <param name="file">Object content</param>
        /// <param name="owoption">Overwrite option</param>
        /// <returns>Return created object</returns>
        public static T Create(T fileobj, Stream file, OverWriteOption owoption)
        {
            return Create(fileobj, file, owoption, null);
        }
        /// <summary>
        /// Create object with content and progress when upload
        /// </summary>
        /// <param name="locationid">Location id where you want to object create</param>
        /// <param name="name">Object name</param>
        /// <param name="file">Object content</param>
        /// <param name="owoption">Overwrite option</param>
        /// <param name="handler">Progress handler for watch progress when uploading</param>
        /// <returns>Returns created object</returns>
        public static T Create(T fileobj, Stream file, OverWriteOption owoption, RequestProgressHandler handler)
        {
            T obj = default(T);
            if (string.IsNullOrEmpty((fileobj as AFiles<T>).parent_id))
                (fileobj as AFiles<T>).parent_id = "me/skydrive";
            try
            {
                QueryParameters qp = new QueryParameters();
                qp.Add("overwrite", GetOverWriteOption(owoption));
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build((fileobj as AFiles<T>).parent_id + "/files/" + (fileobj as AFiles<T>).name, qp), method = "PUT", content_type = "application/json;odata=verbose", Data = new byte[] { 0 } };
                obj = Requester.Request<T>(ro).ElementAt(0);
                //fileobj = obj;// as AOperational<T>).name;
                fileobj = Get((obj as AOperational<T>).id);
            }
            catch (Exception e)
            {
                throw e;
            }
            string[] lid = (fileobj as AFiles<T>).parent_id.Split('.');
            string usrid = User.Get("").id;
            try
            {
                UploadContent(file, usrid, (fileobj as AFiles<T>).name, lid[lid.Length - 1], 0, handler, null, obj);
            }
            catch (Exception e)
            {
                throw e;
            }
            return obj;
        }
        /// <summary>
        /// Upload content of object
        /// </summary>
        /// <param name="file">Object content</param>
        /// <param name="usrid">Owner of location. If null or empty logged user is used</param>
        /// <param name="name">Object name</param>
        /// <param name="locationid">Location id where you want to create object</param>
        /// <param name="fposition">Position in stream. It is used when handled exception and continue upload</param>
        /// <param name="handler">Handler which is used for watch upload progress</param>
        /// <param name="br">Bits request object</param>
        /// <param name="resobj">Result object. It is created object given when exception durin uploading process</param>
        static void UploadContent(Stream file, string usrid, string name, string locationid, long fposition, RequestProgressHandler handler, BitsRequest br, object resobj)
        {
            if (br == null)
            {
                string url = "https://cid-" + usrid + ".users.storage.live.com/items/" + locationid + "/" + name;
                br = new BitsRequest { url = url, total_length = (int)file.Length };
                br.StartSession();
                br.method = "POST";
                try
                {
                    Requester.Request<T>(br);
                }
                catch (Exception e)
                { throw e; }
            }
            file.Position = fposition;
            while (file.Position < file.Length)
            {
                br.method = "POST";
                long position = file.Position;
                int c = br.count;
                br.Data = new byte[file.Length - file.Position > br.MaxLenght ? br.MaxLenght : file.Length - file.Position];
                file.Read(br.Data, 0, br.Data.Length);
                try
                {
                    if (handler != null)
                        Requester.Request<T>(br, handler);
                    else
                        Requester.Request<T>(br);
                }
                catch (Exception e)
                {
                    br.count = c;
                    throw new BITSException("", e, file, position, br, Continue, handler, resobj);
                }
            } 
            br.method = "POST";
            br.content_length = 0;
            br.CommitSession();
            try
            {
                Requester.Request<T>(br);
            }
            catch (Exception e)
            {
                throw new BITSException("", e, file, file.Length, br, Continue, handler, resobj);
            }
        }
        /// <summary>
        /// Can continue upload when exception occured during upload
        /// </summary>
        /// <param name="e">Exception object</param>
        static void Continue(BITSException e)
        {
            try
            {
                UploadContent(e.file, "", "", "", e.position, e.handler, e.request, e.Result);
            }
            catch (Exception ex)
            { throw ex; }
        }
        /// <summary>
        /// Delete current object
        /// </summary>
        public virtual void Delete()
        {
            try
            {
                Requester.Request<T>(new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "DELETE" });
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Move object into new location
        /// </summary>
        /// <param name="newlocationid">Location id where you want to move object</param>
        public virtual void Move(string newlocationid)
        {
            if (string.IsNullOrEmpty(newlocationid))
                newlocationid = "me/skydrive";
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "MOVE", content_type = "application/json" };
                ro.SetData("{ \"destination\": \"" + newlocationid + "\"}");
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Copy object into location
        /// </summary>
        /// <param name="newlocationid">Location id where you want to copy object</param>
        public virtual void Copy(string newlocationid)
        {
            if (string.IsNullOrEmpty(newlocationid))
                newlocationid = "me/skydrive";
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id), method = "COPY", content_type = "application/json" };
                ro.SetData("{ \"destination\": \"" + newlocationid + "\"}");
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Update properties of object. Use this method when you want to rename object
        /// </summary>
        public virtual void Update()
        {
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id.ToString()), method = "PUT", content_type = "application/json" };
                ro.SetData(JSONDataBuilder.Build(this));
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Download content of object
        /// </summary>
        /// <param name="destination">Where downloaded data will be saved</param>
        /// <param name="handler">Handler for progress when downloading</param>
        public void Download(Stream destination, RequestProgressHandler handler)
        {
            try
            {
                T obj = Get(id);
                HttpWebRequest wr = (HttpWebRequest)WebRequest.Create((obj as AFiles<T>).source);
                wr.Timeout = 20000;
                HttpWebResponse wres = (HttpWebResponse)wr.GetResponse();
                byte[] buffer;
                long read = 0;
                wres.GetResponseStream().CopyTo(destination);
                
                Stream s = wres.GetResponseStream();
                while (read < wres.ContentLength)
                {
                    int bufferlenght = Convert.ToInt32(wres.ContentLength - read > download_buffer_size ? download_buffer_size : wres.ContentLength - read);
                    buffer = new byte[bufferlenght];
                    
                    s.Read(buffer, 0, bufferlenght);
                    destination.Write(buffer, 0, bufferlenght);
                    read += buffer.Length;
                    if (handler != null)
                        handler.ProgressChange((int)read, read / (wres.ContentLength / (double)100));
                }
                
            }
            catch(Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// Uploaded content of object
        /// </summary>
        /// <param name="file">Object content</param>
        /// <param name="append">When true download content and append new content at the end of current content, else overwrite current content by new content</param>
        public virtual void UpdateContent(Stream file, bool append = false)
        {
            if (append)
            {
                string f = Path.GetTempFileName();
                using (FileStream fs = new FileStream(f, FileMode.Create))
                {
                    Download(fs, null);
                    file.CopyTo(fs);
                }
                file = new FileStream(f, FileMode.Open);
            }
            string usrid = User.Get("").id;
            try
            {
                if (string.IsNullOrEmpty(parent_id))
                {
                    T obj = Get(id);
                    string[] lid = (obj as AFiles<T>).parent_id.Split('.');
                    UploadContent(file, usrid, name, lid[lid.Length - 1], 0, null, null, null);
                }
                else
                {
                    string[] lid = parent_id.Split('.');
                    UploadContent(file, usrid, name, lid[lid.Length - 1], 0, null, null, null);
                }
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="locationid">Location id where you want to search object</param>
        /// <param name="name">Name of object</param>
        /// <returns>Returns true when object exists in location otherwise false</returns>
        public static bool ExistsByName(string locationid, string name)
        {
            foreach (File f in Folder.GetFiles(locationid))
            {
                if (f.type != "folder" && f.type != "album")
                {
                    if (f.name.ToLower() == name.ToLower())
                        return true;
                }
            }
            return false;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="locationid">Location id where you want to search</param>
        /// <param name="objectid">Object id which you want to search</param>
        /// <returns>Returns true where object exists in location otherwise false</returns>
        public static bool ExistsById(string locationid, string objectid)
        {
            foreach (File f in Folder.GetFiles(locationid))
            {
                if (f.type != "folder" && f.type != "album")
                {
                    if (f.id.ToLower() == objectid.ToLower())
                        return true;
                }
            }
            return false;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns>Returns list of comments for object</returns>
        public IEnumerable<Comment> GetComments()
        {
            try
            {
                return Requester.Request<Comment>(new RequestObject { url = UrlBuilder1.Build(id) + "/comments" }).ToList();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    public abstract class AWithTag<T> : AFiles<T>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <returns>Returns tags of object</returns>
        public virtual IEnumerable<Tag> GetTags()
        {
            try
            {
                return Requester.Request<Tag>(new RequestObject { url = UrlBuilder1.Build(id) + "/tags" }).ToList();
            }
            catch(Exception e)
            { throw e; }
        }
        public void CreateTag(Tag tag)
        {
            try
            {
                RequestObject ro = new RequestObject { url = UrlBuilder1.Build(id) + "/tags", method = "POST", content_type = "application/json" };
                ro.SetData(JSONDataBuilder.Build(tag));
                Requester.Request<T>(ro);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    public class BITSException : ApplicationException
    {
        public Stream file;
        public long position;
        public BitsRequest request;
        public RequestProgressHandler handler;
        public delegate void ContinueHandler(BITSException exception);
        public object Result;

        public ContinueHandler Continue;

        public BITSException(string message, Exception e, Stream file, long position, BitsRequest request, ContinueHandler handler, RequestProgressHandler phandler, object result) :base(message, e)
        {
            this.position = position;
            this.request = request;
            this.file = file;
            this.Continue += handler;
            this.handler = phandler;
            Result = result;
        }
    }
}
